<?php
/**	
*	FILENAME: 		/libraries/novasis.php
*	DESCRIPTION:	This library that uses the PHP Snoopy library to interact with Villanova's NOVASIS system.
*	AUTHOR:			Casey Burkhardt
*	VERSION:		1.0.0
*	LAST MODIFIED:	1/5/2009
**/

require_once($_SERVER['DOCUMENT_ROOT'] . "/config/environment.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/libraries/Snoopy.class.php");

// Define Constants
global $NOVASIS_AUTH_URL, $NOVASIS_COURSE_DETAIL_URL, $NOVASIS_MIDTERM_GRADE_TERM_SELECT_URL, $NOVASIS_MIDTERM_GRADES_URL, $NOVASIS_FINAL_GRADES_URL, $PLAIN_TEXT_LINE_DELIMITER, $HEADER_DATA;
$NOVASIS_AUTH_URL = "https://novasis.villanova.edu/pls/bannerprd/twbkwbis.P_ValLogin";
$NOVASIS_COURSE_DETAIL_URL = "https://novasis.villanova.edu/pls/bannerprd/bwskcrse.P_CrseSchdDetl";
$NOVASIS_MIDTERM_GRADE_TERM_SELECT_URL = "https://novasis.villanova.edu/pls/bannerprd/bwskmgrd.p_write_term_selection";
$NOVASIS_MIDTERM_GRADES_URL = "https://novasis.villanova.edu/pls/bannerprd/bwskmgrd.p_write_midterm_grades";
$NOVASIS_FINAL_GRADES_URL = "https://novasis.villanova.edu/pls/bannerprd/bwskogrd.P_ViewGrde";
$PLAIN_TEXT_LINE_DELIMITER = "\r";
$HEADER_DATA = getHTTPRequestHeaders();

/**	
*	DESCRIPTION:	Called whenever an HTTP Header element contains a Set-Cookie directive.  Parses the cookie and returns it as Name => Value.
**/
function parseCookie($val) {
	$equalsSign = strpos($val, "=");
	$cookieName = substr($val, strlen("Set-Cookie: "), $equalsSign - strlen("Set-Cookie: "));
	$semiColon = strpos($val, ";");
	if ($semiColon !== FALSE)
		$cookieValue = substr($val, $equalsSign + 1, $semiColon - $equalsSign - 1);
	else
		$cookieValue = substr($val, $equalsSign + 1, -2);
	$result[$cookieName] = $cookieValue;
	return $result;
}

/**	
*	DESCRIPTION:	Creates a valid NOVASIS authenticated session and returns an array of session cookies, or FALSE if the session was not created.
**/
function getNOVASISSession($studentID, $pin) {
	global $NOVASIS_AUTH_URL;

	// We start by posting the user supplied login information to the NOVASIS Login Form.
	// Create a new instance of Snoopy, the page fetching / parsing engine.
	$authPage = new Snoopy;
	
	// We can set a timeout, in case this thing doesn't work, but the NOVASIS server is unreliable, so we'll leave it be for now.
	// TODO: Determine if we need a timeout set here.
	//	$authPage -> read_timeout = 5;
	
	// We will need to fetch pages using HTTPS, so we must point Snoopy to our instance of cURL.
	// This information is stored in our environment configuration.
	$authPage -> curl_path = getCurlPath();

	// Because we're sending GET parameters over HTTPS, Snoopy performs some excess escpaing, which we don't want.
	$authPage -> escape_shell = FALSE;

	// Set up appropriate HTTP header data.
	global $HEADER_DATA;
	$authPage -> rawheaders = $HEADER_DATA;

	// NOVASIS requires two basic cookies to track a session.
	// $cookies["TESTID"] will always contain the value "set"
	// $cookies["SESSID"] will contain the validly obtained session ID from NOVASIS
	$cookies["TESTID"] = "set";
	$cookies["SESSID"] = "";
	$authPage -> cookies = $cookies;
	
	if ($authPage -> fetch($NOVASIS_AUTH_URL . "?sid=" . $studentID . "&PIN=" . $pin)) {
	while(list($key, $val) = each($authPage -> headers)) {
			if(strpos($val, "Set-Cookie: ") !== FALSE) {
				// If the header contains a cookie, set that cookie
				$authPage -> cookies = array_merge($authPage -> cookies, parseCookie($val));
			}
		}
	} else {
		// Downloading the remote page failed, return FALSE.
		return FALSE;
	}

	// Check the validity of the session.
	if (strlen($authPage -> cookies["SESSID"]) > 0) {
		// Session is valid, return the cookie array for use in other requests.
		return $authPage -> cookies;
	} else {
		// The session was not validly created, so return FALSE.
		return FALSE;
	}
}

/**	
*	DESCRIPTION:	Returns an array of all term names and IDs for which registration information is availible in the form: Term ID => Term Name.
**/
function getTermIDsWithRegistrationData($sessionData) {
	// The methadology for accomplishing this isn't as clean as it should be, but assuming the system is implemented as it should be, it will work properly.
	// Basically, we're going to download the Midterm Grades -> Select Term page and parse out the items within the drop-down box.
	// The terms listed there are those for which the student was registered for classes at Villanova.

	global $NOVASIS_MIDTERM_GRADE_TERM_SELECT_URL, $HEADER_DATA;
	
	// We must now fetch the midterm grade semester selection page.
	// Set up Snoopy with the required parameters.
	$midtermPage = new Snoopy;
	$midtermPage -> curl_path = getCurlPath();
	$midtermPage -> encode_cookies = FALSE;
	$midtermPage -> escape_shell = FALSE;
	$midtermPage -> cookies = $sessionData;
	$midtermPage -> rawheaders = $HEADER_DATA;
	
	if (!$midtermPage -> fetch($NOVASIS_MIDTERM_GRADE_TERM_SELECT_URL)) {
		// Downloading the remote page failed, return FALSE.
		return FALSE;
	}

	// Isolate the form field options we're interested in.  This will allow us to easily iterate over the data.
	$searchQueryStart = '<OPTION VALUE="';
	$searchQueryEnd = '</SELECT>';
	$startIndex = strpos($midtermPage -> results, $searchQueryStart);
	$endIndex = strpos($midtermPage -> results, $searchQueryEnd, $startIndex);
	$dataBlock = trim(substr($midtermPage -> results, $startIndex, $endIndex - $startIndex));

	// Remove the extraneous SELECTED attribute from the first option tag.
	$dataBlock = str_replace(" SELECTED", "", $dataBlock);
	// Remove the extraneous <OPTION VALUE=" string at the start of each line.
	$dataBlock = str_replace ('<OPTION VALUE="', "", $dataBlock);

	// To split the data, we'll delimit using \n.
	$termLines = explode("\n", $dataBlock);
	// And for each line, we'll split into the final term data array by using the leftover HTML tag remnant.
	$termData = array();
	$delimiter = '">';
	foreach ($termLines as $line) {
		$startIndex = strpos($line, $delimiter);
		$endIndex = $startIndex + strlen($delimiter);
		$termID = trim(substr($line, 0, $startIndex));
		$termName = trim(substr($line, $endIndex));
		$termLine["term_id"] = $termID;
		$termLine["term_name"] = $termName;
		$termData[] = $termLine;
	}

	return $termData;
}

/**	
*	DESCRIPTION:	Returns the term ID of the current academic semester, assuming the student has registered for that semester.  Otherwise, it will return the ID for the last semester for which the student has registered.
**/
function getCurrentTermID($sessionData) {
	// The methadology for accomplishing this isn't as clean as it should be, but assuming the system is implemented as it should be, it will work properly.
	// Basically, we're going to download the Midterm Grades -> Select Term page and parse out the items within the drop-down box.
	// The first <option> tag contains the current term ID and name.

	global $NOVASIS_MIDTERM_GRADE_TERM_SELECT_URL, $HEADER_DATA;
	
	// We must now fetch the midterm grade semester selection page.
	// Set up Snoopy with the required parameters.
	$midtermPage = new Snoopy;
	$midtermPage -> curl_path = getCurlPath();
	$midtermPage -> encode_cookies = FALSE;
	$midtermPage -> escape_shell = FALSE;
	$midtermPage -> cookies = $sessionData;
	$midtermPage -> rawheaders = $HEADER_DATA;
	
	if (!$midtermPage -> fetch($NOVASIS_MIDTERM_GRADE_TERM_SELECT_URL)) {
		// Downloading the remote page failed, return FALSE.
		return FALSE;
	}

	// We are now going to parse the actual data from the result page.
	// First, obtain the start and end indicies of the term ID we're interested in.
	$searchQueryStart = '<OPTION VALUE="';
	$searchQueryEnd = '" SELECTED>';
	$startIndex = strpos($midtermPage -> results, $searchQueryStart) + strlen($searchQueryStart);
	$endIndex = strpos($midtermPage -> results, $searchQueryEnd, $startIndex);

	// Isolate and trim the term ID.
	$termID = trim(substr($midtermPage -> results, $startIndex, $endIndex - $startIndex));
	
	// Next, obtain the start and end endicies of the term name.
	$startIndex = $endIndex + strlen($searchQueryEnd);
	$searchQueryEnd = '<OPTION';
	$endIndex = strpos($midtermPage -> results, $searchQueryEnd, $startIndex);
	
	// Isolate and trim the term name.
	$termName = trim(substr($midtermPage -> results, $startIndex, $endIndex - $startIndex));

	// Validate the Term ID.
	// We know it is always going to be six digits.
	if (!(strlen($termID) == 6)) {
		// That's not what was expected, bail out and return FALSE.
		return FALSE;	
	}

	// Everything seems to have checked out.  Return the term data.
	$termData["term_id"] = $termID;
	$termData["term_name"] = $termName;
	return $termData;
}

/**	
*	DESCRIPTION:	Returns an array representing the information about all courses for which a student is registered in a given term.
**/
function getRegisteredCourseData($sessionData, $termID) {
	global $NOVASIS_COURSE_DETAIL_URL, $PLAIN_TEXT_LINE_DELIMITER, $HEADER_DATA;
	
	// We must now fetch the course detail page.
	// Set up Snoopy with the required parameters.
	$coursePage = new Snoopy;
	$coursePage -> curl_path = getCurlPath();
	$coursePage -> encode_cookies = FALSE;
	$coursePage -> escape_shell = FALSE;
	$coursePage -> cookies = $sessionData;
	$coursePage -> rawheaders = $HEADER_DATA;
	
	if (!$coursePage -> fetchtext($NOVASIS_COURSE_DETAIL_URL . "?term_in=" . $termID)) {
		// Downloading the remote page failed, return FALSE.
		return FALSE;
	}
	
	// We are now going to parse the actual data from the result page.
	// First, obtain the start and end indicies of the course data block.
	$startSearchQuery = "Instructor";
	$endSearchQuery = "Total Credits";
	$startIndex = strpos($coursePage -> results, $startSearchQuery) + strlen($startSearchQuery);
	$endIndex = strpos($coursePage -> results, $endSearchQuery);
	
	// Isolate and trim the course data.
	$dataBlock = trim(substr($coursePage -> results, $startIndex, $endIndex - $startIndex));

	// Allocate the delimited data into an array.
	$courseData = explode($PLAIN_TEXT_LINE_DELIMITER, $dataBlock);

	// Check if the resutling array contains the correct number of fields.  Each courses consists of 12 piecies of data.
	// So, if our array is a length that's a multiple of 12, format the data, otherwise cause the script to fail.
	if (count($courseData) % 12 == 0) {
		$formattedData = array();
		// There's also the possibility of there being no course data.  In this case, we'll return an empty array.
		if (count($courseData) == 0) {
			return $formattedData;
		} else {
			// Otherwise, we'll iterate over the array until we construct an array of all course informaiton.
			for ($curPos = 0; $curPos < count($courseData); $curPos += 12) {
				// Check if the next set of data contains only a meeting time.
				if (strlen($courseData[$curPos]) <= 1) {
						// Add the meeting time to the previous course's meeting time array.
						$formattedData[count($formattedData) - 1]["meeting_times"][] = array (
																						"days"			=> 	$courseData[$curPos + 8],
																						"time"			=> 	$courseData[$curPos + 9],
																						"location"		=> 	$courseData[$curPos + 10],
																						"instructor"	=> 	$courseData[$curPos + 11]
																						);
				} else {
					// Add the new course data to the array of formatted data.
					$formattedData[] = array (
										"crn"			=>	$courseData[$curPos],
										"course"		=>	$courseData[$curPos + 1],
										"title"			=> 	$courseData[$curPos + 2],
										"campus"		=> 	$courseData[$curPos + 3],
										"credits"		=> 	$courseData[$curPos + 4],
										"level"			=> 	$courseData[$curPos + 5],
										"start_date"	=> 	$courseData[$curPos + 6],
										"end_date"		=> 	$courseData[$curPos + 7],
										"meeting_times" => array (
																array (
																	"days"			=> 	$courseData[$curPos + 8],
																	"time"			=> 	$courseData[$curPos + 9],
																	"location"		=> 	$courseData[$curPos + 10],
																	"instructor"	=> 	$courseData[$curPos + 11]
																)
															)
										);
				}
			}
			// Return the formatted course data.
			return $formattedData;
		}
	} else {
		return FALSE;	
	}
}

/**	
*	DESCRIPTION:	Returns an array representing the information about all courses as they relate to midterm grades for a given semester.
**/
function getMidtermGradeInformation($sessionData, $termID) {
	global $NOVASIS_MIDTERM_GRADES_URL, $PLAIN_TEXT_LINE_DELIMITER, $HEADER_DATA;
	
	// Set up Snoopy with the required parameters in order to fetch the midterm grades page.
	$midtermPage = new Snoopy;
	$midtermPage -> curl_path = getCurlPath();
	$midtermPage -> encode_cookies = FALSE;
	$midtermPage -> escape_shell = FALSE;
	$midtermPage -> cookies = $sessionData;
	$midtermPage -> rawheaders = $HEADER_DATA;
	
	if (!$midtermPage -> fetchtext($NOVASIS_MIDTERM_GRADES_URL . "?term_in=" . $termID)) {
		// Downloading the remote page failed, return FALSE.
		return FALSE;
	}
		
	// We are now going to parse the actual data from the result page.
	// First, handle the case of there being no grade data.  We'll return an empty array.
	$noData = strpos($midtermPage -> results, "No grades on your record are available for this term.");
	if ($noData !== FALSE) {
		return array();
	}
	
	// Obtain the start and end indicies of the course and grade data block.
	$startSearchQuery = "Credits" . $PLAIN_TEXT_LINE_DELIMITER . "Level";
	$endSearchQuery = "Skip to top of page";
	$startIndex = strpos($midtermPage -> results, $startSearchQuery) + strlen($startSearchQuery);
	$endIndex = strpos($midtermPage -> results, $endSearchQuery);
	
	// Isolate and trim the course data.
	$dataBlock = trim(substr($midtermPage -> results, $startIndex, $endIndex - $startIndex));

	// Allocate the delimited data into an array.
	$gradeData = explode($PLAIN_TEXT_LINE_DELIMITER, $dataBlock);
		
	// Check if the resutling array contains the correct number of fields.  Each grade consists of 9 piecies of data.
	// So, if our array is a length that's a multiple of 9, format the data, otherwise cause the script to fail.
	if (count($gradeData) % 9 == 0) {
		$formattedData = array();
		$curPos = 0;
		while ($curPos < count($gradeData) - 1) {
			$formattedData[] = array (
								"crn"		=>	$gradeData[$curPos],
								"subject"	=>	$gradeData[$curPos + 1],
								"course"	=> 	$gradeData[$curPos + 2],
								"section"	=> 	$gradeData[$curPos + 3],
								"title"		=> 	$gradeData[$curPos + 4],
								"campus"	=> 	$gradeData[$curPos + 5],
								"grade"		=> 	$gradeData[$curPos + 6],
								"credits"	=> 	$gradeData[$curPos + 7],
								"level"		=> 	$gradeData[$curPos + 8]
								);
			$curPos = $curPos + 9;
		}
		return $formattedData;
	} else {
		return FALSE;	
	}
}

/**	
*	DESCRIPTION:	Returns an array representing the information about all courses as they relate to final grades for a given semester.
**/
function getFinalGradeInformation($sessionData, $termID) {
	global $NOVASIS_FINAL_GRADES_URL, $PLAIN_TEXT_LINE_DELIMITER, $HEADER_DATA;
	
	// Set up Snoopy with the required parameters in order to fetch the midterm grades page.
	$finalPage = new Snoopy;
	$finalPage -> curl_path = getCurlPath();
	$finalPage -> encode_cookies = FALSE;
	$finalPage -> escape_shell = FALSE;
	$finalPage -> cookies = $sessionData;
	$finalPage -> rawheaders = $HEADER_DATA;
	
	if (!$finalPage -> fetchtext($NOVASIS_FINAL_GRADES_URL . "?term_in=" . $termID)) {
		// Downloading the remote page failed, return FALSE.
		return FALSE;
	}
		
	// We are now going to parse the actual data from the result page.
	// First, handle the case of there being no grade data.  We'll return an empty array.
	$noData = strpos($finalPage -> results, "No grades on your record are available for this term.");
	if ($noData !== FALSE) {
		return array();
	}
	
	// Obtain the start and end indicies of the course and grade data block.
	$startSearchQuery = "Quality Points";
	$endSearchQuery = "Undergraduate Summary";
	$startIndex = strpos($finalPage -> results, $startSearchQuery) + strlen($startSearchQuery);
	$endIndex = strpos($finalPage -> results, $endSearchQuery);
	
	// Isolate and trim the course data.
	$dataBlock = trim(substr($finalPage -> results, $startIndex, $endIndex - $startIndex));

	// Allocate the delimited data into an array.
	$gradeData = explode($PLAIN_TEXT_LINE_DELIMITER, $dataBlock);
		
	// Check if the resutling array contains the correct number of fields.  Each grade consists of 11 piecies of data, plus one extraneous one on all but the last result.
	// So, if our array's length plus 1 is a multiple of 12, format the data, otherwise cause the script to fail.
	if ((count($gradeData) + 1) % 12 == 0) {
		$formattedData = array();
		$curPos = 0;
		while ($curPos < count($gradeData) - 1) {
			$formattedData[] = array (
								"crn"				=>	$gradeData[$curPos],
								"subject"			=>	$gradeData[$curPos + 1],
								"course"			=> 	$gradeData[$curPos + 2],
								"section"			=> 	$gradeData[$curPos + 3],
								"title"				=> 	$gradeData[$curPos + 4],
								"campus"			=> 	$gradeData[$curPos + 5],
								"grade"				=> 	$gradeData[$curPos + 6],
								"attempted"			=> 	$gradeData[$curPos + 7],
								"earned"			=> 	$gradeData[$curPos + 8],
								"gpa_hours"			=> 	$gradeData[$curPos + 9],
								"quality_points"	=> 	$gradeData[$curPos + 10]								
								);
			// We advance $curPos 12 here because the final grade form has an extraneous colum that's being picked up by the parser.
			$curPos = $curPos + 12;
		}
		return $formattedData;
	} else {
		return FALSE;	
	}
}
?>